#ifndef TRANSFORM_H
#define TRANSFORM_H

#include "mathengine.h"

namespace raster {

using mathengine::Vec2f;
using mathengine::Vec3f;
using mathengine::Vec4f;
using mathengine::Matrix44f;

class Transform
{
public:

    Transform() : m(Matrix44f::identity()), im(m) { }
    Transform(const float mm[4][4]) {
        m = Matrix44f(
            {mm[0][0], mm[0][1], mm[0][2], mm[0][3],
             mm[1][0], mm[1][1], mm[1][2], mm[1][3],
             mm[2][0], mm[2][1], mm[2][2], mm[2][3],
             mm[3][0], mm[3][1], mm[3][2], mm[3][3]});
        if (m.det() != 0) {
            im = m.inverse();
        } else {
            im = m;
        }
    }
    Transform(float d[16]) {
        m = Matrix44f(
            {d[0],  d[1],  d[2],  d[3],
             d[4],  d[5],  d[6],  d[7],
             d[8],  d[9],  d[10], d[11],
             d[12], d[13], d[14], d[15]});
        if (m.det() != 0) {
            im = m.inverse();
        } else {
            im = m;
        }
    }
    Transform(float d0,  float d1,  float d2,  float d3,
              float d4,  float d5,  float d6,  float d7,
              float d8,  float d9,  float d10, float d11,
              float d12, float d13, float d14, float d15) {
        m = Matrix44f(
            {d0,  d1,  d2,  d3,
             d4,  d5,  d6,  d7,
             d8,  d9,  d10, d11,
             d12, d13, d14, d15});
        if (m.det() != 0) {
            im = m.inverse();
        } else {
            im = m;
        }
    }
    Transform(const Matrix44f& mm) : m(mm) {
        if (m.det() != 0) {
            im = m.inverse();
        } else {
            im = m;
        }
    }
    Transform(const Matrix44f& mm, const Matrix44f& imm) : m(mm), im(imm) { }
    Transform(const Transform& v) : m(v.m), im(v.im) { }
    Transform& operator=(const Transform& v) {
        m = v.m;
        im = v.im;
        return *this;
    }

    bool operator==(const Transform& v) const {
        return (m == v.m);
    }

    bool operator!=(const Transform& v) const {
        return !(m == v.m);
    }

    Transform operator*(float s) const {
        return Transform{this->m * s};
    }
    Transform operator*(const Transform& v) const {
        Matrix44f tmp = m * v.m;
        return Transform{tmp, tmp.inverse()};
    }

#if 0
    Vec3f operator()(const Vec3f& v) const {
        Vec4f v1{v.data[0], v.data[1], v.data[2], 1};
        v1 = m * v1;
        Vec3f v2{v.data[0], v.data[1], v.data[2]};
        return v2;
    }
#endif

    Vec4f operator()(const Vec4f& v) const {
         return m * v;
    }

    Transform& invert() {
        if (invalid()) return *this;

        std::swap(m, im);
        return *this;
    }

    Transform inverse() const {
        if (invalid()) return *this;

        return Transform{im, m};
    }

    /// n' = Sn, S = (M^{-1})^T, M is vertex transform, usually mv transform (model + view transfrom)
    ///
    /// https://www.cnblogs.com/fortunely/p/18178861
    Transform toNormal() const {
        Matrix44f sm = im.transposed();
        Matrix44f sim = sm.inverse();
        return Transform{sm, sim};
    }

    // do not invoke it in ctor if use solution 2
    bool invalid() const {
        // solution 1: m.det() == 0,
        // solution 2: m * im != Matrix44f::identity()
        return !(m * im == Matrix44f::identity());
    }

    friend std::ostream& operator<<(std::ostream& os, const Transform& v) {
        return os << v.m;
    }

private:
    Matrix44f m;
    Matrix44f im; // inverse matrix of m
};

Transform operator*(float s, const Transform& t);

Transform Translate(float tx, float ty, float tz); // Translate a point from one position to another
Transform Translate(const Vec3f& tv);
Transform Scale(float sx, float sy, float sz);     // Scale relative to the origin
Transform Scale(const Vec3f& sv);
Transform RotateX(float theta); // Rotate around the x-asix by theta angle
Transform RotateY(float theta);
Transform RotateZ(float theta);
Transform Rotate(float nx, float ny, float nz, float theta);  // Rotate around axis(nx,ny,nz) by theta angle
Transform Rotate(const Vec3f& axis, float theta); // Rotate around axis by theta angle

Transform Modeling(float tx, float ty, float tz,
                   float sx, float sy, float sz,
                   float nx, float ny, float nz, float theta); // translate + scale + rotate around axis(nx,ny,nz) by theta angle
Transform Modeling(const Vec3f& translate,
                   const Vec3f& scale,
                   const Vec3f& axis, float theta); // translate + scale + rotate around axis by theta angle
Transform Camera(const Vec3f& eye_pos,
                 const Vec3f& gaze_dir,
                 const Vec3f& view_up); // Camera Transformation = View transformation
Transform Orthographic(float left, float right, float bottom, float top, float near, float far);
Transform Orthographic(const Vec3f& lbn, const Vec3f& rtf);
Transform Perspective(float left, float right, float bottom, float top, float near, float far);
Transform Perspective(float neardis, float fardis, float fovY, float aspect);
Transform Viewport(int width, int height);
Transform Viewport(int width, int height, const Vec2f& left_corner);


Transform CoordAToCoordB(const Vec3f& u, const Vec3f& v, const Vec3f& n, const Vec3f& b_origin);

Transform ToNormalTransform(const Transform& vertexTransform); // vertex transform to normal transform

} // namespace raster

#endif // TRANSFORM_H
